package com.pro.framework.mybatisplus;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ClassUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.pro.framework.api.FrameworkConst;
import com.pro.framework.api.clazz.ClassCaches;
import com.pro.framework.api.database.AggregateResult;
import com.pro.framework.api.database.GroupBy;
import com.pro.framework.api.database.TimeQuery;
import com.pro.framework.api.database.page.IPageInput;
import com.pro.framework.api.database.page.PageInput;
import com.pro.framework.api.model.IBaseModel;
import com.pro.framework.api.model.IModel;
import com.pro.framework.api.structure.Tuple2;
import com.pro.framework.api.util.CollUtils;
import com.pro.framework.api.util.DateUtils;
import com.pro.framework.api.util.LambdaUtil;
import com.pro.framework.api.util.StrUtils;
import com.pro.framework.api.util.inner.FunSerializable;
import com.pro.framework.api.util.inner.SerializedLambdaData;
import com.pro.framework.mtq.service.multiwrapper.entity.IMultiPageResult;
import com.pro.framework.mtq.service.multiwrapper.entity.IMultiReadService;
import com.pro.framework.mtq.service.multiwrapper.entity.IMultiService;
import com.pro.framework.mybatisplus.model.BaseUpdater;
import com.pro.framework.mybatisplus.wrapper.*;
import jakarta.annotation.PostConstruct;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.Transactional;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 全量定制 MyXXXWrapper,主要为了UserAi,User等定制实体的ServiceImpl,不会报错
 */
@Slf4j
@SuppressWarnings("UnusedReturnValue")
public abstract class BaseService<M extends BaseMapper<T>, T extends IModel> extends ServiceImpl<M, T> implements IMultiService<T> {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    protected String entityName = null;

    @PostConstruct
    public void init() {
        entityName = StrUtils.firstToLowerCase(getEntityClass().getSimpleName());
    }

    @Autowired
    protected IMultiReadService<T> readService;

    @Override
    public LambdaQueryChainWrapper<T> lambdaQuery() {
        return new MyLambdaQueryChainWrapper<>(baseMapper);
    }

    @Override
    public LambdaUpdateChainWrapper<T> lambdaUpdate() {
        return new MyLambdaUpdateChainWrapper<>(baseMapper);
    }

    // 查询器
    public QueryWrapper<T> qw() {
        return new MyQueryWrapper<>();
    }

    // 更新器
    public UpdateWrapper<T> uw() {
        return new MyUpdateWrapper<>();
    }

    public T getFirst(T t, SFunction<T, Object> prop) {
        return this.lambdaQuery().setEntity(t).orderByAsc(prop).last("limit 1").one();
    }

    public T getOne(T t) {
        return this.lambdaQuery().setEntity(t).one();
    }

    public List<T> list(T t) {
        return this.lambdaQuery().setEntity(t).list();
    }

    public Map<Long, T> idMap(Collection<Long> ids) {
        return ids.isEmpty() ? Collections.emptyMap() : this.listByIds(ids)
                .stream()
                .collect(Collectors.toMap(T::getId, o -> o));
    }

    public List<T> listByIds(List<Long> idList) {
        return idList.isEmpty() ? Collections.emptyList() : super.listByIds(idList);
    }

    /**
     * 加减某些字段
     * update user set login_times = login_times + 1
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean addIncreaseField(Serializable id, SFunction<T, ?> prop, Number number) {
        return this.addIncreaseField((query) -> query.eq(T::getId, id), List.of(new Tuple2<>(prop, number)));
    }

    /**
     * 加减某些字段
     * update user set login_times = login_times + 1
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean addIncreaseField(
            Consumer<LambdaUpdateChainWrapper<T>> query,
            List<Tuple2<SFunction<T, ?>, Number>> addNums
    ) {
        // 查询条件
        LambdaUpdateChainWrapper<T> uw = lambdaUpdate();
        query.accept(uw);
        // 累加属性
        String setSql = addNums.stream().map(addNum -> {
            String fieldName = MybatisPlusUtil.getFieldName(addNum.getT1());
            Number number = addNum.getT2();
            return fieldName + " = " + fieldName + " + " + number;
        }).collect(Collectors.joining(","));
        if (IBaseModel.class.isAssignableFrom(getEntityClass())) {
            setSql = setSql + ", update_time = '" + DateUtils.toStr(LocalDateTime.now()) + "'";
        }
        uw.setSql(setSql);
        return uw.update();
    }

//    /**
//     * 加减某个字段
//     * update user set login_times = login_times + 1
//     *
//     * @return
//     */
//    public boolean addIncreaseField(Serializable id, String fieldName, Number number) {
//        if (StrUtils.isBlank(fieldName) || null == number) {
//            return false;
//        }
//        fieldName = StrUtils.camelToUnderline(fieldName);
//        TableInfo tableInfo = TableInfoHelper.getTableInfo(getEntityClass());
//        Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!");
//        String keyProperty = tableInfo.getKeyProperty();
//        return addIncreaseField(keyProperty, id, fieldName, number);
//    }

//    private boolean addIncreaseField(String keyField, Serializable keyValue, String fieldName, Number number) {
//        return this.update().setSql(fieldName + " = " + fieldName + " + " + number).eq(keyField, keyValue).update();
//    }

    public List<T> count(T params) {
        return this.count(params, null, null, null, null);
    }

    public List<T> count(T params, List<String> amountFields, List<String> groupByFields, String lastSql, TimeQuery timeQuery) {
        QueryWrapper<T> wrapper = MybatisPlusUtil.create(params, timeQuery);
        if (amountFields == null) {
            //默认查询所有可聚合属性
            amountFields = Arrays.stream(ClassUtil.getDeclaredFields(params.getClass()))
                    .filter(f -> {
                        Class<?> type = f.getType();
                        return (!Modifier.isTransient(f.getModifiers()))
                                && (Integer.class.equals(type) || (Long.class.equals(type) && !f.getName()
                                .toLowerCase()
                                .endsWith("id")) || BigDecimal.class.equals(type))
                                ;
                    }).map(f -> {
                        String field = StrUtils.camelToUnderline(f.getName());
                        return "sum(" + field + ") " + field;
                    }).collect(Collectors.toList());
        } else {
            amountFields = new ArrayList<>(amountFields);
        }

        amountFields.add("count(1) count");
        wrapper.select(amountFields.toArray(String[]::new));
        if (CollUtil.isNotEmpty(groupByFields)) {
            amountFields.addAll(groupByFields);
            wrapper.groupBy(groupByFields);
        }
        wrapper.last(StrUtils.isNotBlank(lastSql), lastSql);
        return this.list(wrapper).stream().filter(Objects::nonNull).collect(Collectors.toList());
    }

    public IMultiPageResult<T> selectPage(IPageInput pageInput, Map<String, Object> paramMap, TimeQuery timeQuery) {
        return this.selectPage(entityName, pageInput, paramMap, timeQuery);
    }

    public T selectOneById(Serializable id) {
        return this.selectOneById(entityName, id);
    }

    public T selectOne(String entityClassName, IPageInput pageInput, Map<String, Object> paramMap, TimeQuery timeQuery) {
        if (pageInput == null) {
            pageInput = new PageInput();
        }
        pageInput.setPageSize(1L);
        IMultiPageResult<T> pageRs = this.selectPage(entityClassName, pageInput, paramMap, timeQuery);
        return !pageRs.getRecords().isEmpty() ? pageRs.getRecords().get(0) : null;
    }

    public List<T> selectList(Map<String, Object> paramMap, TimeQuery timeQuery) {
        return this.selectList(entityName, paramMap, timeQuery, null, null, null, null);
    }

    public List<AggregateResult> selectCountSum(IPageInput pageInput, Map<String, Object> paramMap, TimeQuery timeQuery, GroupBy groupBy) {
        return this.selectCountSum(entityName, pageInput, paramMap, timeQuery, groupBy);
    }

    /**
     * 按某个属性差查询 并按他分组
     */
    public <PROP> List<T> getList(SFunction<T, PROP> propFun, PROP propValue) {
        return getList(propFun, propValue, null);
    }

    /**
     * 按某个属性差查询 并按他分组
     */
    public <PROP> List<T> getList(SFunction<T, PROP> propFun, PROP propValue, T param) {
        return getListMap(propFun, Collections.singletonList(propValue), param).getOrDefault(propValue, Collections.emptyList());
    }

    /**
     * 按某个属性差查询 并按他分组
     */
    public <PROP> T getFirst(SFunction<T, PROP> propFun, PROP propValue) {
        List<T> list = getListMap(propFun, Collections.singletonList(propValue), (T) null).get(propValue);
        return list.isEmpty() ? null : list.get(0);
    }

    /**
     * 按某个属性差查询 并按他分组
     */
    public <PROP> Map<PROP, List<T>> getListMap(SFunction<T, PROP> inPropFun, Collection<PROP> inPropValues) {
        return getListMap(inPropFun, inPropValues, null, null);
    }

    /**
     * 按某个属性差查询 并按他分组
     */
    public <PROP> Map<PROP, List<T>> getListMap(SFunction<T, PROP> inPropFun, Collection<PROP> inPropValues, T param) {
        return getListMap(inPropFun, inPropValues, null, param);
    }

    public <PROP> Map<PROP, List<T>> getListMap(SFunction<T, PROP> inPropFun, Collection<PROP> inPropValues, List<SFunction<T, ?>> selectPropFunList) {
        return getListMap(inPropFun, inPropValues, selectPropFunList, null);

    }

    public <PROP> Map<PROP, List<T>> getListMap(SFunction<T, PROP> inPropFun, Collection<PROP> inPropValues, List<SFunction<T, ?>> selectPropFunList, T param) {
        if (inPropValues.isEmpty()) {
            return Collections.emptyMap();
        }
        return orderBy(this.lambdaQuery()
                .setEntity(param)
                .select(selectPropFunList)
                .in(inPropFun, inPropValues))
                .list()
                .stream().collect(Collectors.groupingBy(inPropFun));
    }

    protected LambdaQueryChainWrapper<T> orderBy(LambdaQueryChainWrapper<T> wrapper) {
        return wrapper;
    }

    /**
     * 查询并,按唯一属性,转成Map <key,Entity> map
     *
     * @param queryProp 查询条件
     * @param keyFun    唯一属性
     * @param <KEY>     唯一属性类型泛型
     * @return <key,Entity> map
     */
    public <PROP, KEY> Map<KEY, T> getMap(SFunction<T, PROP> queryProp, Collection<PROP> propValues, Function<T, KEY> keyFun) {
        if (propValues.isEmpty()) {
            return Collections.emptyMap();
        }
        return getMap(qw -> qw.in(queryProp, propValues), keyFun);
    }

    public <PROP, KEY> Map<KEY, T> getMap(String queryProp, Collection<PROP> propValues, Function<T, KEY> keyFun) {
        QueryWrapper<T> qw = this.qw();
        qw.in(queryProp, propValues);
        // 查询并分组
        return CollUtils.listToMap(this.list(qw), keyFun);
    }

    public <KEY> Map<KEY, T> getMap(Consumer<LambdaQueryWrapper<T>> qwConsumer, Function<T, KEY> keyFun) {
        LambdaQueryWrapper<T> qw = MyWrappers.lambdaQuery();
//        LambdaQueryChainWrapper<T> qw = this.lambdaQuery();
        qwConsumer.accept(qw);
        // 查询并分组
        return CollUtils.listToMap(this.list(qw), keyFun);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeById(Class<?> clazz, Serializable id) {
        return this.removeById(id);
    }

    @Override
    public IMultiPageResult<T> selectPage(String entityClassNames, IPageInput pageInput, Map<String, Object> paramMap, TimeQuery timeQuery) {
        return readService.selectPage(StrUtils.or(entityClassNames, entityName), pageInput, paramMap, timeQuery);
    }

    @Override
    public T selectOneById(String entityClassName, Serializable id) {
        return readService.selectOneById(entityName, id);
    }

    @Override
    public List<T> selectList(String entityClassNames, Map<String, Object> paramMap, TimeQuery timeQuery, List<String> selects, List<String> selectMores, List<String> selectLess, PageInput pageInput) {
        return readService.selectList(StrUtils.or(entityClassNames, entityName), paramMap, timeQuery, selects,
                selectMores, selectLess, pageInput);
    }

    @Override
    public List<AggregateResult> selectCountSum(String entityClassName, IPageInput pageInput, Map<String, Object> paramMap, TimeQuery timeQuery, GroupBy groupBy) {
        return readService.selectCountSum(entityName, pageInput, paramMap, timeQuery, groupBy);
    }

    @Override
    public boolean save(T entity) {
        return super.save(entity);
    }

    @Override
    public boolean saveBatch(Collection<T> entityList) {
        return super.saveBatch(entityList);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveOrUpdate(T entity) {
        return super.saveOrUpdate(entity);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeBatchByIds(Collection<?> list) {
        return super.removeBatchByIds(list);
    }

    @SafeVarargs
    public final boolean updatePropsById(T entity, FunSerializable<T, ?>... updateProps) {
        UpdateWrapper<T> uw = this.uw();
        uw.eq("id", entity.getId());
        for (FunSerializable<T, ?> prop : updateProps) {
            String propName = LambdaUtil.resolveCache(prop).getPropName();
            Object value = prop.apply(entity);
            uw.set(StrUtils.camelToUnderline(propName), value);
        }
        return this.update(uw);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateById(T entity) {
        return super.updateById(entity);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateBatchById(Collection<T> entityList) {
        return super.updateBatchById(entityList.stream().sorted(Comparator.comparing(T::getId)).toList());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveOrUpdateBatch(Collection<T> entityList) {
        if (entityList == null) {
            return false;
        }
        return super.saveOrUpdateBatch(entityList.stream()
                .filter(Objects::nonNull)
                .sorted(Comparator.comparing(T::getId, Comparator.nullsLast(Comparator.naturalOrder())))
                .toList());
//        return super.saveOrUpdateBatch(entityList.stream().sorted(Comparator.comparing(T::getId,Comparator.nullsLast(Comparator.naturalOrder()))).toList());
    }
    //    public List<T> selectLists(String entityClassNames, IdModel request) {
//        List<Long> ids = request.toIds();
//        return this.selectLists(entityClassNames, ids);
//    }

//    public List<T> selectLists(String entityClassNames, List<Long> ids) {
//        Map<Long, T> map = this.selectMap(entityClassNames, ids);
//        return ids.stream().map(map::get).filter(Objects::nonNull).collect(Collectors.toList());
//    }

    public Map<Long, T> selectMap(String entityClassNames, List<Long> ids) {
        if (ids.isEmpty()) {
            return Collections.emptyMap();
        }
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("id",
                "#in#" + ids.stream().map(Object::toString).collect(Collectors.joining(FrameworkConst.Str.split)));
        List<T> list = selectList(entityClassNames, paramMap, null, null, null, null, null);
        return CollUtils.listToMap(list, T::getId);
    }


    public Page<T> page(Page<T> page, T param) {
        return this.page(page, Wrappers.lambdaQuery(param));
    }

    @Transactional(rollbackFor = Exception.class)
    public void updateBatchByIds(Collection<? extends Serializable> ids, BaseUpdater<T, ?> updater) {
        //noinspection unchecked
        this.updateBatchByIds(ids, new BaseUpdater[]{updater});
    }

    @SuppressWarnings("unchecked")
    @Transactional(rollbackFor = Exception.class)
    public void updateBatchByIds(Collection<? extends Serializable> ids, BaseUpdater<T, ?>... updaters) {
        if (ids.isEmpty()) {
            return;
        }
        LambdaUpdateChainWrapper<T> wrapper = this.lambdaUpdate();
        for (BaseUpdater<T, ?> updater : updaters) {
            wrapper.set(updater.getPropFunc(), updater.getPropValue());
        }
        wrapper.in(T::getId, ids).update();
    }

    @SneakyThrows
    public T newInstance() {
        //noinspection deprecation
        return this.getEntityClass().newInstance();
    }

    /**
     * 一对一实体可以在service中使用
     */
    protected <PROP> Map<PROP, T> getMap(SFunction<T, PROP> queryProp, Collection<PROP> propValues) {
        return CollUtils.toFirstElementMap(this.getListMap(queryProp, propValues, (T) null));
    }

    @Transactional(rollbackFor = Exception.class)
    public <PROP> boolean removeByPropIds(SFunction<T, PROP> queryProp, Collection<? extends Serializable> propValues) {
        if (propValues.isEmpty()) {
            return false;
        }
        return this.lambdaUpdate()
                .in(queryProp, propValues)
                .remove();
    }

    @Transactional(rollbackFor = Exception.class)
    public <NUM extends Number> boolean batchIncrease(List<T> list, SFunction<T, NUM> columnLambda) {
        if (list == null || list.isEmpty()) return false;
        list = list.stream().sorted(Comparator.comparing(T::getId)).toList();
        String fieldName = MybatisPlusUtil.getFieldName(columnLambda);
        StringBuilder sql = new StringBuilder();
        sql.append("UPDATE ")
                .append(StrUtils.camelToUnderline(getEntityClass().getSimpleName()))
                .append(" SET ")
                .append(fieldName)
                .append(" = CASE id ");

        for (T entity : list) {
            Long id = (Long) BeanUtil.getFieldValue(entity, "id");
            NUM delta = columnLambda.apply(entity);
            sql.append("WHEN ").append(id).append(" THEN ").append(fieldName)
                    .append(" + ").append(delta).append(" ");
        }

        sql.append("ELSE ")
                .append(fieldName)
                .append(" END WHERE id IN (")
                .append(list.stream()
                        .map(e -> String.valueOf(BeanUtil.getFieldValue(e, "id")))
                        .collect(Collectors.joining(",")))
                .append(")");
        return jdbcTemplate.update(sql.toString()) > 0;
    }

    @SuppressWarnings("unchecked")
    @Transactional(rollbackFor = Exception.class)
    public void updateBatchById(List<T> list, FunSerializable<T, ?>... propFuns) {
        this.updateBatchById(list.stream().map(o -> {
            T t = newInstance();
            t.setId(o.getId());
            for (FunSerializable<T, ?> propGetFun : propFuns) {
                Object value = propGetFun.apply(o);
                if (value != null) {
                    this.setPropValue(propGetFun, t, value);
                }
            }
            return t;
        }).toList());
    }

    @SneakyThrows
    private void setPropValue(FunSerializable<T, ?> propFun, T t, Object value) {
        SerializedLambdaData serializedLambdaData = LambdaUtil.resolveCache(propFun);
        String propName = serializedLambdaData.getPropName();
        Method propSetMethod = ClassCaches.getClassInfosFull(getEntityClass()).get(propName).getT2();
        propSetMethod.invoke(t, value);
    }

//    /**
//     * 批量变动 乐观锁 +
//     *
//     * @param list         对象列表
//     * @param columnLambda 变动的数值属性
//     * @param <NUM>        任意数值类型
//     * @return 是否全部成功
//     */
//    @Transactional(rollbackFor = Exception.class)
//    public <NUM extends Number> boolean batchDecreaseWithVersion(List<T> list, SFunction<T, NUM> columnLambda) {
//        if (list == null || list.isEmpty()) return false;
//
//        String fieldName = MybatisPlusUtil.getFieldName(columnLambda);
//        String tableName = StrUtils.camelToUnderline(getEntityClass().getSimpleName());
//
//        StringBuilder sql = new StringBuilder();
//        sql.append("UPDATE ").append(tableName).append(" SET ")
//                .append(fieldName).append(" = CASE ");
//
//        StringBuilder versionSql = new StringBuilder();
//        versionSql.append("version = CASE ");
//
//        List<String> idList = new ArrayList<>();
//        List<String> versionList = new ArrayList<>();
//
//        for (T entity : list) {
//            Long id = (Long) BeanUtil.getFieldValue(entity, "id");
//            NUM delta = columnLambda.apply(entity);
//            Long version = (Long) BeanUtil.getFieldValue(entity, "version");
//
//            // CASE WHEN 写法
//            sql.append("WHEN id = ").append(id).append(" AND version = ").append(version)
//                    .append(" THEN ").append(fieldName).append(" + ").append(delta).append(" ");
//            versionSql.append("WHEN id = ").append(id).append(" AND version = ").append(version)
//                    .append(" THEN version + 1 ");
//
//            idList.add(String.valueOf(id));
//            versionList.add(String.valueOf(version));
//        }
//
//        // 结束 CASE
//        sql.append("ELSE ").append(fieldName).append(" END, ");
//        sql.append(versionSql).append("ELSE version END ");
//
//        // WHERE 条件
//        sql.append("WHERE id IN (").append(String.join(",", idList)).append(")")
//                .append(" AND version IN (").append(String.join(",", versionList)).append(")");
//
//        log.warn("Batch update SQL: {}", sql);
//
//        int updatedRows = jdbcTemplate.update(sql.toString());
//
//        // 检查是否更新了所有记录
//        if (updatedRows != list.size()) {
//            throw new FrameworkException("Expected to update " + list.size() + " records, but only " + updatedRows + " records were updated.");
//        }
//
//        // 检查更新后的字段是否符合要求
//        String checkSql = "SELECT id, " + fieldName + " FROM " + tableName +
//                " WHERE id IN (" + String.join(",", idList) + ") AND " + fieldName + " <= 0";
//        List<Map<String, Object>> invalidRecords = jdbcTemplate.queryForList(checkSql);
//
//        if (!invalidRecords.isEmpty()) {
//            throw new FrameworkException("Quantity value must be greater than zero for records: " + invalidRecords);
//        }
//
//        return true;
//    }


}
